<script lang="ts" module>
    import { getContext, onDestroy, setContext } from "svelte";

    export const FormItemContextKey = Symbol("FormItemContextKey");
    export const useFormItemContext = () => {
        // 确保只能取一次，formItem下的表单元素需要放在第一个
        const ctx = getContext<FormItemContextProps>(FormItemContextKey);
        const onFieldChange = ctx?.onFieldChange;
        const onFieldBlur = ctx?.onFieldBlur;
        setContext(FormItemContextKey, { ...ctx, onFieldChange: undefined, onFieldBlur: undefined });
        return { onFieldChange, onFieldBlur };
    };
</script>

<script lang="ts">
    import type { FormContextOptions } from "../Form/Form";
    import { useFormContext } from "../Form/Form.svelte";
    import { useClassList } from "../utils/useClassList";
    import type { FormItemContextProps, FormItemProps } from "./FormItem";
    import { useStyle } from "../utils/useStyle";
    import { Popover } from "../Popover";
    import AsyncValidator, { type ValidateError } from "async-validator";

    const props: FormItemProps = $props();
    const ctx: FormContextOptions | undefined = useFormContext();
    let error = $state<string>("");

    let itemRef: any = $state();
    const labelAlign = props.labelAlign ?? "auto";
    const errorTransfer = $derived(props.errorTransfer ?? ctx?.errorTransfer?.() ?? false);
    const errorAlign = $derived(props.errorAlign ?? ctx?.errorAlign?.() ?? "right");
    const colon = props.colon ?? ctx?.colon ?? true;
    const hideRequiredMark = props.hideRequiredMark ?? ctx?.hideRequiredMark ?? false;
    const name = props.name;
    const _id = $props.id();
    const id = `form_item_${_id}`;
    if (!props.name) {
        console.warn("formItem needs name property to check valid");
    }
    let isRequired = $derived.by(() => {
        const rules = props.rules || (name && getRule(ctx?.rules?.(), name));
        if (rules && Array.isArray(rules)) {
            return rules.some((rule) => rule.required);
        }
        return false;
    });

    const clazzName = $derived(
        useClassList(props, "cm-form-item", {
            "cm-form-item-error": error,
            "cm-form-item-inline": props.inline || ctx?.inline,
            "cm-form-item-with-colon": colon,
            "cm-form-item-required": isRequired && props.label && !hideRequiredMark,
        })
    );

    const labelStyles = $derived(useStyle({ style: props.labelStyle }, { width: ctx?.labelWidth?.() + "px" }));

    const clearError = () => {
        error = "";
    };

    /**
     * 获取校验规则
     * @param obj
     * @param name
     */
    const getRule = (obj: any, name: string | (string | number)[]) => {
        if (!obj) {
            return undefined;
        }
        if (Array.isArray(name)) {
            return name.reduce((prev, cur) => {
                return prev[cur];
            }, obj);
        } else {
            return obj[name];
        }
    };

    const check = async (v: any, trigger: string) => {
        if (itemRef) {
            const rect = itemRef.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) {
                return null;
            }
        }
        let rules = props.rules || getRule(ctx?.rules?.(), name!);
        if (name && rules) {
            if (trigger === "change") {
                rules = rules.filter((rule: any) => rule.trigger !== "blur");
            }
            return validateByAsyncValidator(v, rules);
        }
        return null;
    };

    const validateByAsyncValidator = async (v: any, rules: any[]) => {
        const descriptor = {
            [`${id}`]: rules,
        };
        const validator = new AsyncValidator(descriptor);
        const model = {
            [`${id}`]: v,
        };

        return new Promise<ValidateError[] | null>((resolve) => {
            validator.validate(model, { firstFields: true }, (errors, fields) => {
                if (errors) {
                    error = errors[0].message!;
                    resolve(errors);
                } else {
                    error = "";
                    resolve(null);
                }
            });
        });
    };

    const onFieldChange = () => {
        if (name) {
            ctx?.onFieldChange(name, "change");
        }
    };
    const onFieldBlur = () => {
        if (name) {
            ctx?.onFieldChange(name, "blur");
        }
    };

    props.name && ctx?.setCheckValid?.(props.name, check);
    props.name && ctx?.setClearValid?.(props.name, clearError);

    onDestroy(() => {
        name && ctx?.removeFieldValid?.(name);
    });

    setContext(FormItemContextKey, {
        onFieldChange,
        onFieldBlur,
    });
</script>

<div class={clazzName} style={props.style}>
    {#if props.label}
        <label
            class={{
                "cm-form-label": true,
                [`cm-form-label-${labelAlign}`]: true,
            }}
            style={labelStyles}
        >
            {#if typeof props.label === "function"}
                {@render props.label()}
            {:else}
                {props.label}
            {/if}
        </label>
    {/if}

    <div class="cm-form-item-element" bind:this={itemRef}>
        <div class="cm-form-item-control">
            {@render props.children?.()}
        </div>
        {#if errorTransfer && error}
            <Popover parent class="cm-form-item-error-popover" arrow align={errorAlign} theme="error" disabled={!error} content={error}></Popover>
        {:else if error}
            <div class="cm-form-item-error-tip">{error}</div>
            <div class="cm-form-item-margin-offset"></div>
        {/if}
    </div>
</div>
